﻿namespace YunQue.Common.Helpers
{
    /// <summary>
    /// PolicyHelper
    /// </summary>
    public class PolicyHelper
    {
        /// <summary>
        /// 重试(不需要返回值)
        /// </summary>
        /// <param name="action"></param>
        /// <param name="retryCount"></param>
        /// <param name="timeSpan"></param>
        public static void Retry(Action action, int retryCount = 3, TimeSpan? timeSpan = null)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            if (timeSpan == null) timeSpan = new TimeSpan(0, 0, 1);
            var policy = Policy.Handle<Exception>(ex => !(ex is NotRetryException))
                               .WaitAndRetry(retryCount, i => timeSpan.Value, (exception, time, index, context) =>
                               {
#if DEBUG
                                   Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")}第{index}次重试");
#endif
                               });
            policy.Execute(() =>
            {
                action();
            });
        }
        /// <summary>
        /// 重试(需要返回值)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="action"></param>
        /// <param name="retryCount"></param>
        /// <param name="timeSpan"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static T Retry<T>(Func<T> action, int retryCount = 3, TimeSpan? timeSpan = null)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            if (timeSpan == null) timeSpan = new TimeSpan(0, 0, 1);
            var policy = Policy.Handle<Exception>(ex => !(ex is NotRetryException))
                               .WaitAndRetry(retryCount, i => timeSpan.Value, (exception, time, index, context) =>
                               {
#if DEBUG
                                   Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")}第{index}次重试");
#endif
                               });
            return policy.Execute(() =>
            {
                return action();
            });
        }
        /// <summary>
        /// 重试(异步不需要返回值)
        /// </summary>
        /// <param name="action"></param>
        /// <param name="retryCount"></param>
        /// <param name="timeSpan"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static async Task RetryAsync(Func<Task> action, int retryCount = 3, TimeSpan? timeSpan = null)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            if (timeSpan == null) timeSpan = new TimeSpan(0, 0, 1);
            var policy = Policy.Handle<Exception>(ex => !(ex is NotRetryException))
                               .WaitAndRetryAsync(retryCount, i => timeSpan.Value, (exception, time, index, context) =>
                               {
#if DEBUG
                                   Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")}第{index}次重试");
#endif
                               });
            await policy.ExecuteAsync(async () =>
            {
                await action();
            }).ConfigureAwait(false);
        }

        /// <summary>
        /// 重试(异步需要返回值)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="action"></param>
        /// <param name="retryCount"></param>
        /// <param name="timeSpan"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static async Task<T> RetryAsync<T>(Func<Task<T>> action, int retryCount = 3, TimeSpan? timeSpan = null)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            if (timeSpan == null) timeSpan = new TimeSpan(0, 0, 1);
            var policy = Policy.Handle<Exception>(ex => !(ex is NotRetryException))
                               .WaitAndRetryAsync(retryCount, i => timeSpan.Value, (exception, time, index, context) =>
                               {
#if DEBUG
                                   Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")}第{index}次重试");
#endif
                               });
            return await policy.ExecuteAsync(async () =>
            {
                return await action();
            }).ConfigureAwait(false);
        }
        /// <summary>
        /// 降级(不需要返回值)
        /// </summary>
        /// <param name="action"></param>
        /// <param name="callback"></param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void Fallback(Action action, Action callback)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            if (callback == null) throw new ArgumentNullException(nameof(callback));
            var fallbackPolicy = Policy.Handle<Exception>().Fallback(() =>
            {
                callback();
            });
            fallbackPolicy.Execute(() =>
            {
                action();
            });
        }
        /// <summary>
        /// 降级(需要返回值)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="action"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static T Fallback<T>(Func<T> action, Func<T> callback)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            if (callback == null) throw new ArgumentNullException(nameof(callback));
            var fallbackPolicy = Policy<T>.Handle<Exception>().Fallback(callback());
            return fallbackPolicy.Execute(() =>
            {
                return action();
            });
        }
        /// <summary>
        /// 降级(异步不需要返回值)
        /// </summary>
        /// <param name="action"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static async Task FallbackAsync(Func<Task> action, Func<Task> callback)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            if (callback == null) throw new ArgumentNullException(nameof(callback));
            var fallbackPolicy = Policy.Handle<Exception>().FallbackAsync(async (ct) =>
            {
                await callback();
            });
            await fallbackPolicy.ExecuteAsync(async () =>
            {
                await action();
            }).ConfigureAwait(false);
        }
        /// <summary>
        /// 降级(异步需要返回值)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="action"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static async Task<T> FallbackAsync<T>(Func<Task<T>> action, Func<Task<T>> callback)
        {
            if (action == null) throw new ArgumentNullException(nameof(action));
            if (callback == null) throw new ArgumentNullException(nameof(callback));

            var fallbackPolicy = Policy<T>
                .Handle<Exception>()
                .FallbackAsync(async (ct) => await callback());

            return await fallbackPolicy.ExecuteAsync(async () => await action()).ConfigureAwait(false);
        }
    }

    /// <summary>
    /// 不需要进行重试的异常
    /// </summary>
    public class NotRetryException : Exception
    {
        /// <summary>
        /// 不需要进行重试的异常
        /// 需要抛出异常，但是不需要重试
        /// </summary>
        /// <param name="message"></param>
        public NotRetryException(string message) : base(message)
        {
        }
        /// <summary>
        /// 不需要进行重试的异常
        /// 需要抛出异常，但是不需要重试
        /// </summary>
        /// <param name="message"></param>
        /// <param name="innerException"></param>
        public NotRetryException(string message, Exception innerException) : base(message, innerException)
        {
        }

    }
}
